//============================================================================
//  Copyright (c) 1996-2005 Winbond Electronic Corporation
//
//  Module Name:
//    wblinux.c
//
//  Abstract:
//    Linux releated routines
//
//============================================================================
#include "sysdef.h"
#include "new_wireless.h"

u32
WBLINUX_MemoryAlloc(void* *VirtualAddress, u32 Length)
{
	*VirtualAddress = kmalloc( Length, GFP_ATOMIC ); //GFP_KERNEL is not suitable

	if (*VirtualAddress == NULL)
		return 0;
	return 1;
}

s32
EncapAtomicInc(PADAPTER Adapter, void* pAtomic)
{
	PWBLINUX pWbLinux = &Adapter->WbLinux;
	u32	ltmp;
	PULONG	pltmp = (PULONG)pAtomic;
	OS_SPIN_LOCK_ACQUIRED( &pWbLinux->AtomicSpinLock );
	(*pltmp)++;
	ltmp = (*pltmp);
	OS_SPIN_LOCK_RELEASED( &pWbLinux->AtomicSpinLock );
	return ltmp;
}

s32
EncapAtomicDec(PADAPTER Adapter, void* pAtomic)
{
	PWBLINUX pWbLinux = &Adapter->WbLinux;
	u32	ltmp;
	PULONG	pltmp = (PULONG)pAtomic;
	OS_SPIN_LOCK_ACQUIRED( &pWbLinux->AtomicSpinLock );
	(*pltmp)--;
	ltmp = (*pltmp);
	OS_SPIN_LOCK_RELEASED( &pWbLinux->AtomicSpinLock );
	return ltmp;
}

unsigned char
WBLINUX_Initial(PADAPTER Adapter)
{
	PWBLINUX pWbLinux = &Adapter->WbLinux;

	OS_SPIN_LOCK_ALLOCATE( &pWbLinux->SpinLock );
	OS_SPIN_LOCK_ALLOCATE( &pWbLinux->AtomicSpinLock );
	pWbLinux->netdev->open = wb35_open;
	pWbLinux->netdev->stop = wb35_close;
	pWbLinux->netdev->hard_start_xmit = wb35_start_xmit;
	pWbLinux->netdev->set_multicast_list = wb35_set_multicast;
	pWbLinux->netdev->get_stats = wb35_netdev_stats;
	pWbLinux->netdev->wireless_handlers = (struct iw_handler_def *)&test_iw_handlers;

	return TRUE;
}

void
WBLinux_ReceivePacket(PADAPTER Adapter, PRXLAYER1 pRxLayer1)
{
	PWBLINUX pWbLinux = &Adapter->WbLinux;
	struct sk_buff *skb = NULL;
	PUCHAR		BufAddr;
	u32		ByteCount = 0;
	u32		i;
	u16		BufSize;

	for( i=0; i<pRxLayer1->BufferNumber; i++ ) {
		BufSize = pRxLayer1->BufferQueue[i].BufferSize;
		ByteCount += BufSize;
	}
#ifdef _PE_RX_DUMP_
	WBDEBUG(( "[w35und]---> Rx total length =%d BuffNum=%d\n", ByteCount, pRxLayer1->BufferNumber ));
#endif

	if ((skb = dev_alloc_skb( ByteCount + 2 )) != NULL) {
		skb->dev = pWbLinux->netdev;
		skb_reserve( skb, 2 );
        
		for (i=0; i<pRxLayer1->BufferNumber; i++) {
			BufSize = pRxLayer1->BufferQueue[i].BufferSize;
			BufAddr = pRxLayer1->BufferQueue[i].pBufferAddress;
			// I Think this fits better - Scythe - 
			memcpy(skb->data, BufAddr, BufSize);
			skb_put( skb, BufSize );
		}

		skb->protocol = eth_type_trans( skb, pWbLinux->netdev );

		netif_rx( skb );
		pWbLinux->netdev->last_rx = jiffies;
		pWbLinux->stats.rx_packets ++;
		pWbLinux->stats.rx_bytes += ByteCount;
	} else {
#ifdef _PE_RX_DUMP_
		WBDEBUG(( "[w35und]RX SKB ALLOCATE IS FAILED !\n" ));
#endif
	}
}

int wb35_start_xmit(struct sk_buff *skb, struct net_device *netdev)
{
	PADAPTER	Adapter = netdev->priv;
	PWBLINUX	pWbLinux = &Adapter->WbLinux;
	PUCHAR		pBufAddress = (PUCHAR)skb->data;
	unsigned char	IsStop = FALSE;

#ifdef _PE_TX_DUMP_
	WBDEBUG(( "[w35und]wb35_start_xmit->\n" ));
	WBDEBUG(( "[w35und]skb=%x size=%d\n", skb, skb->len ));
#endif

	// Basic check ---------------------------------------------------
	if (!skb || (skb->len<(DOT_3_TYPE_OFFSET+2)) || pWbLinux->shutdown || !CURRENT_LINK_ON ||
	    (
#ifdef _WPA2_
		    CURRENT_DESIRED_WPA2_ENABLE &&
#endif
		    CURRENT_DESIRED_WPA_ENABLE && CURRENT_CONTROL_PORT_BLOCK && (*(PUSHORT)(pBufAddress+12) != cpu_to_le16(0x8e88)) ) || \
	    ((psLOCAL->RadioOffStatus.boHwRadioOff == TRUE) || (psLOCAL->RadioOffStatus.boSwRadioOff == TRUE))) {
#ifdef _PE_TX_DUMP_
		WBDEBUG(( "[w35und] wb35_start_xmit SK_BUFF IS NOT OK ! len=%d\n", skb->len ));
#endif
		dev_kfree_skb( skb );
		return 0; // Ignore this packet
	}

	// Check room space ---------------------------------------------
	if (pWbLinux->skb_array[pWbLinux->skb_SetIndex])
		return -EBUSY;

	// Storing skb into skb_array and move point --------------------
	pWbLinux->skb_array[ pWbLinux->skb_SetIndex ] = skb;
	pWbLinux->skb_SetIndex++;
	pWbLinux->skb_SetIndex %= WBLINUX_PACKET_ARRAY_SIZE;

	// Does driver need to stop OS sending?
	if ((pWbLinux->skb_array[pWbLinux->skb_SetIndex]!=NULL) && !pWbLinux->netif_state_stop) {
		OS_SPIN_LOCK_ACQUIRED( &pWbLinux->SpinLock );
		if (!pWbLinux->netif_state_stop) {
			pWbLinux->netif_state_stop = 1;
			IsStop = TRUE;
		}
		OS_SPIN_LOCK_RELEASED( &pWbLinux->SpinLock );

		if (IsStop) {
#ifdef _PE_TX_DUMP_
			WBDEBUG(("[w35und] tx netif stop"));
#endif
			netif_stop_queue( netdev );
		}
	}

	// Calling Mds for sending packet --------------------
	Adapter->sLocalPara._NumTxMSDU++;
	netdev->trans_start=jiffies;
	Mds_Tx(Adapter);

	return 0;// return 1 if asking kernel retry to send sk-buffer.
}

void
WBLINUX_GetNextPacket(PADAPTER Adapter,  PDESCRIPTOR pDes)
{
	PWBLINUX	pWbLinux = &Adapter->WbLinux;
	struct sk_buff  *pSkb;
	u8 	        *pSkbBuffer;
	int		buf_size;

	//Get a Linux packet to transmit
	pSkb = pWbLinux->skb_array[ pWbLinux->skb_GetIndex ];
	pWbLinux->skb_array[ pWbLinux->skb_GetIndex ] = NULL;

	// Move the point to the next
	pWbLinux->skb_GetIndex++;
	pWbLinux->skb_GetIndex %= WBLINUX_PACKET_ARRAY_SIZE;

	//================================================================================
	// Fill the related information into descriptor												//
	//================================================================================
	pWbLinux->packet_return = pSkb;
	pSkbBuffer = pSkb->data;
	buf_size = pSkb->len;
	pWbLinux->stats.tx_packets++; // 20060331 move here
	pWbLinux->stats.tx_bytes += buf_size; // 20060331 move here

	//931130.5.t
	//		+------------+
	//		| header + 2 |  <-- DA + SA + T/L
	//		+------------+
	//		+-------------------------------+
	//		| data							|
	//		+-------------------------------+
	//					:
	//					:
	//
	DESCRIPTOR_ADD_BUFFER( pDes, pSkbBuffer, DOT_3_TYPE_OFFSET+2 );
	buf_size -= (DOT_3_TYPE_OFFSET+2);
	pSkbBuffer += (DOT_3_TYPE_OFFSET+2);

	if (buf_size)
		DESCRIPTOR_ADD_BUFFER( pDes, pSkbBuffer, (u16)buf_size );
}

void
WBLINUX_GetNextPacketCompleted(PADAPTER Adapter, PDESCRIPTOR pDes)
{
	PWBLINUX	pWbLinux = &Adapter->WbLinux;
	s32		room_space = 0;
	struct sk_buff *pSkb;
	unsigned char		IsStart = FALSE;

	// Return the send result to LINUX
	pSkb = pWbLinux->packet_return;
	pWbLinux->packet_return = NULL;
	if (in_irq())
		dev_kfree_skb_irq(pSkb);
	else
		dev_kfree_skb(pSkb);
	MIB_GS_XMIT_OK_INC;

	// Trying to wakeup the netif if necessary
	if (pWbLinux->netif_state_stop)	{
		room_space = pWbLinux->skb_GetIndex - pWbLinux->skb_SetIndex;
		if (room_space < 0) // Ex: + + G + S + + +
			room_space += WBLINUX_PACKET_ARRAY_SIZE;

		OS_SPIN_LOCK_ACQUIRED( &pWbLinux->SpinLock );
		if (pWbLinux->netif_state_stop) {
			pWbLinux->netif_state_stop = 0;
			IsStart = TRUE;
		}
		OS_SPIN_LOCK_RELEASED( &pWbLinux->SpinLock );

		if (IsStart) {
#ifdef _PE_TX_DUMP_
			WBDEBUG(("[w35und] tx netif start---"));
#endif
			netif_wake_queue( pWbLinux->netdev );
		}
	}
}

struct net_device_stats * wb35_netdev_stats( struct net_device *netdev )
{
   PADAPTER Adapter = (PADAPTER)netdev->priv;
   return &Adapter->WbLinux.stats;
}

void wb35_set_multicast( struct net_device *netdev )
{
   PADAPTER	Adapter = (PADAPTER)netdev->priv;
   phw_data_t	pHwData = &Adapter->sHwData;
   struct dev_mc_list *mcptr;
   u8           Multcst_enable=0;
   int i=0;

   // netif_stop_queue( netdev ); not necessary for 35
   if( netdev->flags & IFF_PROMISC )
     {
	//not support for this mode
#ifdef _PE_STATE_DUMP_
	WBDEBUG(( "[w35und] %s: promiscuous mode", netdev->name ));
#endif
     }
   else if( (netdev->mc_count > DEFAULT_MULTICASTLISTMAX) || (netdev->flags & IFF_ALLMULTI) )
     {
	//not support for this mode
#ifdef _PE_STATE_DUMP_
	WBDEBUG(( "[w35und] %s: allmulti set", netdev->name ));
#endif
     }
   else if( (netdev->flags & IFF_MULTICAST) || (netdev->mc_count != 0) )
     {
	//multicast packet with multicast address
	Multcst_enable=1;
	// set multicast address into regester
	//
	for( mcptr = netdev->mc_list; mcptr; mcptr=mcptr->next )
	  {
	     memcpy( Adapter->Mds.MulticastAddressesArray[i],
			     mcptr->dmi_addr, MAC_ADDR_LENGTH );
	     i++;
	  }
	Adapter->Mds.MulticastListNo = (u8)netdev->mc_count;
	hal_set_multicast_address( &Adapter->sHwData,
				   (PUCHAR)Adapter->Mds.MulticastAddressesArray,
				   (u8)Adapter->Mds.MulticastListNo );
     }

   hal_set_accept_multicast( pHwData, Multcst_enable );
   //netif_wake_queue(netdev); not necessary for 35
}

void
WBLINUX_Destroy(PADAPTER Adapter)
{
	PWBLINUX pWbLinux = &Adapter->WbLinux;

	DEBUG2("UNregistering....\n");

	WBLINUX_stop( Adapter );
	OS_SPIN_LOCK_FREE( &pWbNdis->SpinLock );
	unregister_netdev( pWbLinux->netdev );
#ifdef _PE_USB_INI_DUMP_
	WBDEBUG(("[w35und] unregister_netdev!\n"));
#endif
}

void
WBLINUX_stop(  PADAPTER Adapter )
{
	PWBLINUX	pWbLinux = &Adapter->WbLinux;
	struct sk_buff *pSkb;

	if (OS_ATOMIC_INC( Adapter, &pWbLinux->ThreadCount ) == 1) {
		// Shutdown module immediately
		pWbLinux->shutdown = 1;

		while (pWbLinux->skb_array[ pWbLinux->skb_GetIndex ]) {
			// Trying to free the un-sending packet
			pSkb = pWbLinux->skb_array[ pWbLinux->skb_GetIndex ];
			pWbLinux->skb_array[ pWbLinux->skb_GetIndex ] = NULL;
			if( in_irq() )
				dev_kfree_skb_irq( pSkb );
			else
				dev_kfree_skb( pSkb );

			pWbLinux->skb_GetIndex++;
			pWbLinux->skb_GetIndex %= WBLINUX_PACKET_ARRAY_SIZE;
		}

#ifdef _PE_STATE_DUMP_
		WBDEBUG(( "[w35und] SKB_RELEASE OK\n" ));
#endif
	}

	OS_ATOMIC_DEC( Adapter, &pWbLinux->ThreadCount );
}

void
WbWlanHalt(  PADAPTER Adapter )
{
	//---------------------
	Adapter->sLocalPara.ShutDowned = TRUE;

	Mds_Destroy( Adapter );

	// Halt
	Roam_Halt(Adapter);
	SCAN_Halt(Adapter);
	MLME_Halt(Adapter);
	SME_Halt(Adapter);

	// Turn off Rx and Tx hardware ability
	hal_stop( &Adapter->sHwData );
#ifdef _PE_USB_INI_DUMP_
	WBDEBUG(("[w35und] Hal_stop O.K.\n"));
#endif
	OS_SLEEP(100000);// Waiting Irp completed

	// Destroy the NDIS module
	WBLINUX_Destroy( Adapter );

	// Halt the HAL
	hal_halt(&Adapter->sHwData, NULL);
}

unsigned char
WbWLanInitialize(PADAPTER Adapter)
{
	phw_data_t	pHwData;
	PUCHAR		pMacAddr, pMacAddr2;
	u32		InitStep = 0;
	u8		EEPROM_region;
	u8		HwRadioOff;

	do {
		//
		// Setting default value for Linux
		//
		Adapter->sLocalPara.region_INF = REGION_AUTO;
		Adapter->sLocalPara.TxRateMode = RATE_AUTO;
		psLOCAL->bMacOperationMode = MODE_802_11_BG;	// B/G mode
		Adapter->Mds.TxRTSThreshold = DEFAULT_RTSThreshold;
		Adapter->Mds.TxFragmentThreshold = DEFAULT_FRAGMENT_THRESHOLD;
		hal_set_phy_type( &Adapter->sHwData, RF_WB_242_1 );
		Adapter->sLocalPara.MTUsize = MAX_ETHERNET_PACKET_SIZE;
		psLOCAL->bPreambleMode = AUTO_MODE;
		Adapter->sLocalPara.RadioOffStatus.boSwRadioOff = FALSE;
		psSME->bDesiredPowerSave = FALSE;
		psSCAN->BScanEnable = TRUE;
		pHwData = &Adapter->sHwData;
		hal_set_phy_type( pHwData, RF_DECIDE_BY_INF );

		//
		// Initial each module and variable
		//
		if (!WBLINUX_Initial(Adapter)) {
#ifdef _PE_USB_INI_DUMP_
			WBDEBUG(("[w35und]WBNDIS initialization failed\n"));
#endif
			break;
		}

		// Initial Software variable
		Adapter->sLocalPara.ShutDowned = FALSE;

		//added by ws for wep key error detection
		Adapter->sLocalPara.bWepKeyError= FALSE;//added by ws 02/02/04
		Adapter->sLocalPara.bToSelfPacketReceived = FALSE;
		Adapter->sLocalPara.WepKeyDetectTimerCount= 2 * 100; /// 2 seconds

		// Initial USB hal
		InitStep = 1;
		pHwData = &Adapter->sHwData;
		if (!hal_init_hardware(pHwData, Adapter))
			break;

		EEPROM_region = hal_get_region_from_EEPROM( pHwData );
		if (EEPROM_region != REGION_AUTO)
			psLOCAL->region = EEPROM_region;
		else {
			if (psLOCAL->region_INF != REGION_AUTO)
				psLOCAL->region = psLOCAL->region_INF;
			else
				psLOCAL->region = REGION_USA;	//default setting
		}

		// Get Software setting flag from hal
		Adapter->sLocalPara.boAntennaDiversity = FALSE;
		if (hal_software_set(pHwData) & 0x00000001)
			Adapter->sLocalPara.boAntennaDiversity = TRUE;

		//
		// For TS module
		//
		InitStep = 2;
		if (!TS_initial(Adapter))
			break;

		// For MDS module
		InitStep = 3;
		Mds_initial(Adapter);

		//=======================================
		// Initialize the SME, SCAN, MLME, ROAM
		//=======================================
		InitStep = 4;
		SME_Init(Adapter);
		Scan_Init(Adapter);
		if (!MLME_Init(Adapter))
			break;

		MLMESetExcludeUnencrypted(Adapter, FALSE);
		MLMESetMaxReceiveLifeTime(Adapter, DEFAULT_MAX_RECEIVE_TIME);

		InitStep = 5;
		Roam_Init(Adapter);

		InitStep = 6;

		// If no user-defined address in the registry, use the addresss "burned" on the NIC instead.
		pMacAddr = Adapter->sLocalPara.ThisMacAddress;
		pMacAddr2 = Adapter->sLocalPara.PermanentAddress;
		hal_get_permanent_address( pHwData, Adapter->sLocalPara.PermanentAddress );// Reading ethernet address from EEPROM
		if (OS_MEMORY_COMPARE(pMacAddr, "\x00\x00\x00\x00\x00\x00", MAC_ADDR_LENGTH )) // Is equal
		{
			memcpy( pMacAddr, pMacAddr2, MAC_ADDR_LENGTH );
		} else {
			// Set the user define MAC address
			hal_set_ethernet_address( pHwData, Adapter->sLocalPara.ThisMacAddress );
		}

		//get current antenna
		psLOCAL->bAntennaNo = hal_get_antenna_number(pHwData);
#ifdef _PE_STATE_DUMP_
		WBDEBUG(("Driver init, antenna no = %d\n", psLOCAL->bAntennaNo));
#endif
		hal_get_hw_radio_off( pHwData );

		// Waiting for HAL setting OK
		while (!hal_idle(pHwData))
			OS_SLEEP(10000);

		MTO_Init(Adapter);

		HwRadioOff = hal_get_hw_radio_off( pHwData );
		psLOCAL->RadioOffStatus.boHwRadioOff = !!HwRadioOff;

		hal_set_radio_mode( pHwData, (unsigned char)(psLOCAL->RadioOffStatus.boSwRadioOff || psLOCAL->RadioOffStatus.boHwRadioOff) );

		if (TS_RUNNING_IN_TESTSTA_MODE) { 
			TS_STARTUP( Adapter );
		} else {
			K_MSG	sSmeMsg;
			sSmeMsg.wMsgType = SMEMSG_SCAN_REQ;
			sSmeMsg.pMsgPtr = NULL;
			SME_Entry(Adapter, &sSmeMsg);

			OS_SLEEP(1000000);	//1 sec
		}

		hal_driver_init_OK(pHwData) = 1; // Notify hal that the driver is ready now.
		//set a tx power for reference.....
		sme_set_tx_power_level(Adapter, 12);
		return TRUE;
	}
	while(FALSE);

	switch( InitStep )
	{
	case 5: Roam_Halt( Adapter );
	case 4: MLME_Halt( Adapter );
		SCAN_Halt( Adapter );
		SME_Halt( Adapter );
	case 3: Mds_Destroy( Adapter );
	case 2:
	case 1: WBLINUX_Destroy( Adapter );
		hal_halt( pHwData, NULL );
	case 0: break;
	}

	return FALSE;
}

// 20060802
void WBLINUX_ConnectStatus(  PADAPTER Adapter,  u32 flag )
{
   PWBLINUX	pWbLinux = &Adapter->WbLinux;

   pWbLinux->LinkStatus = flag; // OS_DISCONNECTED	or  OS_CONNECTED
   if( flag == OS_DISCONNECTED )
     FourWayClear(Adapter);
}

